//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include "aura.h"
#include "win32.h"
#include "private.h"

PDoAtExceptionalExitOfProcess s_pDoAtExceptionalExitFunc = NULL;
CProcessManager * s_pProcessManager  = NULL;
HANDLE s_hExceptionalExitSurveillant = NULL;

CProcessManager::CProcessManager()
{
    m_lastIndex = 0;
    m_hRewaitEvent = NULL;

    for (Aura_Int32 i = 0; i < MAXIMUM_WAIT_OBJECTS; i++) {
        m_hArrayOfWaitHandles[i] = NULL;
        m_pArrayOfObjects[i]     = NULL;
    }
    InitializeCriticalSection(&m_hArrayLock);
}

CProcessManager::~CProcessManager()
{
    if (NULL != m_hRewaitEvent) {
        CloseHandle(m_hRewaitEvent);
        m_hRewaitEvent = NULL;
    }
    DeleteCriticalSection(&m_hArrayLock);
}

Aura_ECode CProcessManager::Initialize()
{
    m_hRewaitEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
    if (NULL == m_hRewaitEvent) {
        return GetLastECodeError();
    }
    m_hArrayOfWaitHandles[m_lastIndex] = m_hRewaitEvent;
    m_pArrayOfObjects[m_lastIndex++] = NULL;

    return AURA_NOERROR;
}

void CProcessManager::AttachToArray(HANDLE hProcess, ProcessObject * pObject)
{
    PulseEvent(m_hRewaitEvent);  // notify to wake from "Multiple Waiting".
    EnterCriticalSection(&m_hArrayLock);

    assert(m_lastIndex < MAXIMUM_WAIT_OBJECTS);
    pObject->m_index = m_lastIndex;
    m_hArrayOfWaitHandles[m_lastIndex] = hProcess;
    m_pArrayOfObjects[m_lastIndex] = pObject;
    m_lastIndex++;

    LeaveCriticalSection(&m_hArrayLock);
    PulseEvent(m_hRewaitEvent);  // notify to wake from "Single Waiting".

    return ;
}

BOOL CProcessManager::DetachFromArray(DWORD index, ProcessObject ** ppObject)
{
    ProcessObject * pObject = NULL;
    assert(NULL != ppObject);

    EnterCriticalSection(&m_hArrayLock);
    if (index >= m_lastIndex) {
        LeaveCriticalSection(&m_hArrayLock);
        return FALSE;
    }
    {
        //BUGBUG:
        DWORD exitCode = 0;
        BOOL  bSuccess = FALSE;
        bSuccess = GetExitCodeProcess(m_hArrayOfWaitHandles[index], &exitCode);
        if (bSuccess && (STILL_ACTIVE == exitCode)) {
            LeaveCriticalSection(&m_hArrayLock);
            return FALSE;
        }
    }
    pObject = m_pArrayOfObjects[index];
    assert(index == pObject->m_index);
    pObject->m_index = DETACHED_INDEX; // set detached flag.
    *ppObject = pObject;

    DWORD i = index;
    for (; i < (m_lastIndex - 1); i++) {
        m_hArrayOfWaitHandles[i] = m_hArrayOfWaitHandles[i+1];
        m_pArrayOfObjects[i] = m_pArrayOfObjects[i+1];
        m_pArrayOfObjects[i]->m_index = i;
    }
    m_hArrayOfWaitHandles[i] = NULL;
    m_pArrayOfObjects[i] = NULL;
    m_lastIndex--;

    LeaveCriticalSection(&m_hArrayLock);

    return TRUE;
}

BOOL CProcessManager::DetachFromArray(ProcessObject * pObject)
{
    Aura_Bool bSucc = FALSE;
    DWORD index;
    DWORD i;

    PulseEvent(m_hRewaitEvent); // notify to wake from "Multiple Waiting" ;
    EnterCriticalSection(&m_hArrayLock);

    index = pObject->m_index;
    if (DETACHED_INDEX == index) {
        goto Exit;
    }

    assert(index < m_lastIndex);
    assert(pObject == m_pArrayOfObjects[index]);
    pObject->m_index = DETACHED_INDEX;

    i = index;
    for (; i < (m_lastIndex - 1); i++) {
        m_hArrayOfWaitHandles[i] = m_hArrayOfWaitHandles[i+1];
        m_pArrayOfObjects[i] = m_pArrayOfObjects[i+1];
        m_pArrayOfObjects[i]->m_index = i;
    }
    m_hArrayOfWaitHandles[i] = NULL;
    m_pArrayOfObjects[i] = NULL;
    m_lastIndex--;

    bSucc = TRUE;

Exit:
    LeaveCriticalSection(&m_hArrayLock);
    PulseEvent(m_hRewaitEvent); // notify to wake from "Single Waiting".

    return  bSucc;
}

void CProcessManager::SurveilExceptionalExit()
{
    while (TRUE) {
        DWORD dwObject;
        dwObject = WaitForMultipleObjectsEx(m_lastIndex,
                              (const HANDLE *)m_hArrayOfWaitHandles,
                              FALSE,
                              (DWORD)INFINITE,
                              FALSE);
        switch (dwObject) {
        case WAIT_FAILED:
            break;
        case WAIT_OBJECT_0:
            WaitForSingleObjectEx(m_hRewaitEvent, (DWORD)INFINITE, FALSE);

            break;
        default:
            DWORD index = dwObject - WAIT_OBJECT_0;
            ProcessObject * pObject = NULL;
            BOOL bSucc = s_pProcessManager->DetachFromArray(index, &pObject);
            if (bSucc) {
                s_pDoAtExceptionalExitFunc((Aura_ProcessId)pObject->m_processId);
            }
            break;
        }
    }

    return;
}

AURA_API_(Aura_ProcessId) AuraProcess_GetCurrentId()
{
    return GetCurrentProcessId();
}

AURA_API_(void) AuraProcess_Exit(
        /* [in] */ Aura_ExitCode exitCode)
{
    ExitProcess((UINT)exitCode);

    assert(0);
    return ;
}

AURA_API AuraProcess_GetStartInfo(
        /* [out] */ wchar_t * pImageName,
        /* [out] */ wchar_t * pArguments)
{
    wchar_t * cmdLine = NULL;
    INT i = 0;

    cmdLine = (wchar_t *)GetCommandLineW();
    while (*cmdLine != L' ' && *cmdLine != L'\0') {
        pImageName[i] = *cmdLine;
        cmdLine++;
        i++;
    }
    pImageName[i] = L'\0';
    while (*cmdLine == L' ')cmdLine++; // Skip whitespace;

    wcscpy(pArguments, cmdLine);

    return AURA_NOERROR;
}

AURA_API AuraProcess_Create(
        /* [in] */ Aura_PWChar imageName,
        /* [in] */ Aura_PWChar arguments,
        /* [out] */ Aura_Handle * phProcess,
        /* [out] */ Aura_ProcessId * pProcessId)
{
    PROCESS_INFORMATION processInfo;
    STARTUPINFOW startupInfo;
    ProcessObject *pProcObject = NULL;
    Aura_Bool bSucc = FALSE;

    assert(AURA_NULL != imageName);
    assert(L'\0' != (wchar_t)imageName[0]);

    memset((void *)&processInfo, 0, sizeof(PROCESS_INFORMATION));
    memset((void *)&startupInfo, 0, sizeof(STARTUPINFOW));
    startupInfo.cb = sizeof(STARTUPINFOW);

    pProcObject = (ProcessObject*)Aura_malloc(sizeof(ProcessObject));
    if (NULL == pProcObject) {
        return AURA_E_OUT_OF_MEMORY;
    }
    memset(pProcObject, 0, sizeof(ProcessObject));

    wchar_t commandLine[AURA_MAX_PATH] = L"";
    wcscat(commandLine, (wchar_t *)imageName);

    if (AURA_NULL != arguments) {
        wcscat(commandLine, L" ");
        wcscat(commandLine, (wchar_t *)arguments);
    }

    // CreateProcessW. If the function uses the first parameter,
    // it will not use the search path. If the file name does not contain
    // an extension, .exe is not assumed.
    bSucc = CreateProcessW(NULL, commandLine,
                           NULL, NULL, FALSE, 0, NULL, NULL,
                           &startupInfo, &processInfo);
    if (!bSucc) {
        Aura_free(pProcObject);
        return GetLastECodeError();
    }

    CloseHandle(processInfo.hThread);
    if (NULL != phProcess) {
        pProcObject->m_processId = processInfo.dwProcessId;
        pProcObject->m_hProcess  = processInfo.hProcess;
        pProcObject->m_index     = DETACHED_INDEX;

        if (NULL != s_pProcessManager) {
            s_pProcessManager->AttachToArray(processInfo.hProcess, pProcObject);
        }
        *phProcess = (Aura_Handle)pProcObject;
    }
    else {
        CloseHandle(processInfo.hProcess);
        Aura_free((void*)pProcObject);
        pProcObject = NULL;
    }
    if (NULL != pProcessId) {
        *pProcessId = (Aura_ProcessId)processInfo.dwProcessId;
    }

    return AURA_NOERROR;
}

AURA_API AuraProcess_Open(
        /* [in] */ Aura_ProcessId processId,
        /* [out] */ Aura_Handle * phProcess)
{
    ProcessObject * pProcObject = NULL;
    HANDLE hProcess = NULL;
    Aura_ECode ec = AURA_NOERROR;

    assert(NULL != phProcess);

    pProcObject = (ProcessObject *)Aura_malloc(sizeof(ProcessObject));
    if (NULL == pProcObject) {
        return AURA_E_OUT_OF_MEMORY;
    }
    memset((void *)pProcObject, 0, sizeof(ProcessObject));
    hProcess = OpenProcess(ALL_ACCESS_RIGHT, FALSE, (DWORD)processId);
    if (NULL == hProcess) {
        goto E_FAIL_EXIT;
    }
    pProcObject->m_processId = (DWORD)processId;
    pProcObject->m_hProcess = hProcess;
    pProcObject->m_index    = DETACHED_INDEX;

    if (NULL != s_pProcessManager) {
        s_pProcessManager->AttachToArray(pProcObject->m_hProcess, pProcObject);
    }

    *phProcess = (Aura_Handle)pProcObject;
    return AURA_NOERROR;

E_FAIL_EXIT:
    ec = GetLastECodeError();
    if (NULL != pProcObject) {
        Aura_free((void *)pProcObject);
        pProcObject = NULL;
    }
    return ec;
}

AURA_API AuraProcess_Kill(
        /* [in] */ Aura_Handle hProcess,
        /* [in] */ Aura_ExitCode exitCode)
{
    ProcessObject * pProcObject = (ProcessObject *)hProcess;
    BOOL bSuccess = FALSE;

    assert(NULL != hProcess);

    bSuccess = TerminateProcess(pProcObject->m_hProcess, (INT)exitCode);
    if (!bSuccess) {
        goto E_FAIL_EXIT;
    }
    return AURA_NOERROR;

E_FAIL_EXIT:
    return GetLastECodeError();
}

AURA_API AuraProcess_Destroy(
        /* [in] */ Aura_Handle hProcess)
{
    ProcessObject * pProcObject = (ProcessObject *)hProcess;
    BOOL bSuccess = FALSE;
    Aura_ECode ec = AURA_NOERROR;

    assert(NULL != hProcess);

    if (NULL != s_pProcessManager) {
        s_pProcessManager->DetachFromArray(pProcObject);
    }
    bSuccess = CloseHandle(pProcObject->m_hProcess);
    if (!bSuccess) {
        ec = GetLastECodeError();
    }
    Aura_free(pProcObject);

    return ec;
}

AURA_API AuraProcess_GetPriorityRank(
        /* [in] */ Aura_Handle hProcess,
        /* [out] */ Aura_ThreadPriorityRank * pPriorityRank)
{
    ProcessObject * pProcObject = (ProcessObject *)hProcess;
    DWORD dwPriorityClass;

    assert(NULL != hProcess);
    assert(NULL != pPriorityRank);

    dwPriorityClass = GetPriorityClass(pProcObject->m_hProcess);
    if (0 == dwPriorityClass) {
        goto E_FAIL_EXIT;
    }

    *pPriorityRank = WinPriorityClassConvertToAura(dwPriorityClass);
    return AURA_NOERROR;

E_FAIL_EXIT:
    return GetLastECodeError();
}

AURA_API AuraProcess_SetPriorityRank(
        /* [in] */ Aura_Handle hProcess,
        /* [out] */ Aura_ThreadPriorityRank priorityRank)
{
    ProcessObject * pProcObject = (ProcessObject *)hProcess;
    BOOL bSuccess = FALSE;
    DWORD dwPriorityClass;

    assert(NULL != hProcess);
    assert((Aura_ThreadPriorityRank_Normal == priorityRank)
         ||(Aura_ThreadPriorityRank_SoftRealTime == priorityRank));

    dwPriorityClass = AuraPriorityClassConvertToWin(priorityRank);
    bSuccess = SetPriorityClass(pProcObject->m_hProcess, dwPriorityClass);
    if (!bSuccess) {
        goto E_FAIL_EXIT;
    }

    return AURA_NOERROR;

E_FAIL_EXIT:
    return GetLastECodeError();
}

AURA_API AuraProcess_GetLifespan(
        /* [in] */ Aura_Handle hProcess,
        /* [out] */ Aura_SystemTime * pStartTime,
        /* [out] */ Aura_SystemTime * pExitTime)
{
    ProcessObject * pProcObject = (ProcessObject *)hProcess;
    BOOL bSuccess = FALSE;
    FILETIME time[4];

    assert(NULL != hProcess);
    assert((NULL != pStartTime) || (NULL != pExitTime));

    memset((void *)time, 0, sizeof(time));
    bSuccess = GetProcessTimes(pProcObject->m_hProcess,
                           &time[0], &time[1],
                           &time[2], &time[3]);
    if (!bSuccess) {
        goto E_FAIL_EXIT;
    }

    if (NULL != pStartTime) {
        WinFileTimeConvertToAuraSystime(&time[0],pStartTime);
    }
    if (NULL != pExitTime) {
        WinFileTimeConvertToAuraSystime(&time[1],pExitTime);
    }
    return AURA_NOERROR;

E_FAIL_EXIT:
    return GetLastECodeError();
}

AURA_API AuraProcess_GetProcessorTimes(
        /* [in] */ Aura_Handle hProcess,
        /* [out] */ Aura_Millisecond32 * pUserTime,
        /* [out] */ Aura_Millisecond32 * pPrivilegedTime)
{
    ProcessObject * pProcObject = (ProcessObject *)hProcess;
    BOOL bSuccess = FALSE;
    FILETIME time[4] = {0};

    assert(NULL != hProcess);
    assert((NULL != pUserTime) || (NULL != pPrivilegedTime));

    memset((void *)time, 0, sizeof(time));
    bSuccess = GetProcessTimes(pProcObject->m_hProcess,
                           &time[0], &time[1],
                           &time[2], &time[3]);
    if (!bSuccess) {
        goto E_FAIL_EXIT;
    }

    if (NULL != pUserTime) {
        WinFileTimeConvertToMilliseconds(&time[2], pUserTime);
    }
    if (NULL != pPrivilegedTime) {
        WinFileTimeConvertToMilliseconds(&time[3], pPrivilegedTime);
    }

    return AURA_NOERROR;

E_FAIL_EXIT:
    return GetLastECodeError();
}

AURA_API AuraProcess_GetExitCode(
        /* [in] */ Aura_Handle hProcess,
        /* [out] */ Aura_ExitCode * pExitCode)
{
    ProcessObject * pProcObject = (ProcessObject *)hProcess;
    BOOL bSuccess = FALSE;
    DWORD dwExitCode;

    assert(NULL != hProcess);
    assert(NULL != pExitCode);

    bSuccess = GetExitCodeProcess(pProcObject->m_hProcess, &dwExitCode);
    if (!bSuccess) {
        goto E_FAIL_EXIT;
    }

    *pExitCode = (Aura_ExitCode)dwExitCode;

    return AURA_NOERROR;

E_FAIL_EXIT:
    return GetLastECodeError();
}

AURA_API AuraProcess_GetMemoryInformation(
        /* [in] */ Aura_Handle hProcess,
        /* [out] */ Aura_Int32 * pPrivateVirtualMemorySize,
        /* [out] */ Aura_Int32 * pSharedVirtualMemorySize,
        /* [out] */ Aura_Int32 * pPrivatePhysicalMemorySize,
        /* [out] */ Aura_Int32 * pSharedPhysicalMemorySize)
{
    ProcessObject * pProcObject = (ProcessObject *)hProcess;
    PROCESS_MEMORY_COUNTERS memCounter;
    BOOL bSuccess = FALSE;

    assert(NULL != hProcess);
    assert((NULL != pPrivateVirtualMemorySize)
           || (NULL != pSharedVirtualMemorySize)
           || (NULL != pPrivatePhysicalMemorySize)
           || (NULL != pSharedPhysicalMemorySize));

    memset((void *)&memCounter, 0, sizeof(memCounter));
    bSuccess = GetProcessMemoryInfo(pProcObject->m_hProcess,
                                    &memCounter,
                                    sizeof(memCounter));
    if (!bSuccess) {
        goto E_FAIL_EXIT;
    }

    // BUG here.
    if (NULL != pPrivateVirtualMemorySize) {
        *pPrivateVirtualMemorySize = memCounter.WorkingSetSize;
    }
    if (NULL != pSharedVirtualMemorySize) {
        *pSharedVirtualMemorySize = memCounter.WorkingSetSize;
    }
    if (NULL != pPrivatePhysicalMemorySize) {
        *pPrivatePhysicalMemorySize = memCounter.WorkingSetSize;
    }
    if (NULL != pSharedPhysicalMemorySize) {
        *pSharedPhysicalMemorySize = memCounter.WorkingSetSize;
    }

    return AURA_NOERROR;

E_FAIL_EXIT:
    return GetLastECodeError();
}

static DWORD WINAPI SuveilThreadEntry(LPVOID pArgs)
{
    CProcessManager * pManager = (CProcessManager *)pArgs;
    pManager->SurveilExceptionalExit();

    return 0;
}

AURA_API AuraCentralContext_Set(
    /* [in] */ PDoAtExceptionalExitOfProcess pDoAtExtFunc)
{
    DWORD threadId;
    Aura_ECode ec = AURA_NOERROR;

    assert(NULL == s_pProcessManager);
    assert(NULL == s_pDoAtExceptionalExitFunc);
    assert(NULL == s_hExceptionalExitSurveillant);

    s_pProcessManager = new CProcessManager();
    if (NULL == s_pProcessManager) {
        return AURA_E_OUT_OF_MEMORY;
    }
    ec = s_pProcessManager->Initialize();
    if (AURA_FAILED(ec)) {
        goto ErrorExit;
    }

    s_pDoAtExceptionalExitFunc =
                    (PDoAtExceptionalExitOfProcess)pDoAtExtFunc;
    s_hExceptionalExitSurveillant = CreateThread(NULL, 0,
                                        SuveilThreadEntry,
                                        (LPVOID)s_pProcessManager,
                                        0, &threadId);
    if (NULL == s_hExceptionalExitSurveillant) {
        ec = GetLastECodeError();
        goto ErrorExit;
    }

    return AURA_NOERROR;

ErrorExit:
    if (NULL != s_pProcessManager) {
        delete s_pProcessManager;
        s_pProcessManager = NULL;
    }
    return ec;
}

AURA_API_(void) AuraCentralContext_Clear()
{
    if (NULL != s_pProcessManager) {
        delete s_pProcessManager;
        s_pProcessManager = NULL;
    }

    s_pDoAtExceptionalExitFunc = NULL;

    if (NULL != s_hExceptionalExitSurveillant) {
        CloseHandle(s_hExceptionalExitSurveillant);
        TerminateThread(s_hExceptionalExitSurveillant, 0);
        s_hExceptionalExitSurveillant = NULL;
    }

    return ;
}

